import logging
import os
import shutil
from src.util.Service import *
# from gevent.fileobject import FileObject
from werkzeug.datastructures.file_storage import FileStorage
from src.config.FlaskConfig import static_folder, static_url_path


# 文件类
class FileEntry:
    id: int  # 文件编号
    parent: int  # 父文件夹
    fType: str  # 文件类型
    name: str  # 文件名称
    path: str  # 文件路径
    suffix: str  # 文件后缀
    size: int  # 文件大小
    hidden: str  # 是否隐藏
    createBy: str  # 创建人
    createTime: datetime  # 创建时间
    updateBy: str  # 修改人
    updateTime: datetime  # 修改时间
    log: str  # 开启访问日志记录
    extend: str  # 是否继承父权限

    def __init__(self, **op) -> None:
        self.id = op.get('id') or 0
        self.parent = op.get('parent') or 0  # 父文件夹
        self.fType = op.get('fType') or 'file'  # 文件类型
        self.name = op.get('name') or ''  # 文件名称
        self.path = op.get('path') or ''  # 文件路径
        self.suffix = op.get('suffix') or ''  # 文件后缀
        self.size = op.get('size') or 0  # 文件大小
        self.hidden = op.get('hidden') or "N"  # 是否隐藏
        self.createBy = op.get('createBy') or 0  # 创建人
        self.createTime = op.get('createTime') or None  # 创建时间
        self.updateBy = op.get('updateBy') or None  # 修改人
        self.updateTime = op.get('updateTime') or None  # 修改时间
        self.log = op.get('log') or 'N'  # 开启访问日志记录
        self.extend = op.get('extend') or 'Y'  # 是否继承父权限


# 检查一个目录是否为另一个目录的子目录
def is_subdirectory(child: str, parent: str):
    relative = os.path.relpath(child, parent)
    return not (relative.startswith(os.pardir) or relative.startswith(os.curdir)) and not os.path.isabs(relative)


# 获取相对于静态资源路径的相对路径
def formatRelPath(path: str):
    return os.path.relpath(os.path.abspath(path), static_folder).replace('\\', '/')


# 获取以静态资源路径为准的绝对路径
def formatAbsPath(path: str):
    new_path = os.path.join(static_folder, path).replace('\\', '/')
    if is_subdirectory(new_path, static_folder):
        return new_path
    else:
        return static_folder


# 格式化静态资源访问url
def formatStaticUrl(path: str):
    return os.path.join(static_url_path, path).replace("\\", "/")


# 递归获取某文件的权限
def getAuth(file: FileEntry, auth_type: str = None):
    if file.extend == 'N':
        return FileAuth.getAll(file.id, auth_type)
    elif file.extend == 'Y':
        # 检查是否为根目录文件
        if file.parent == 0:
            return FileAuth.getAll(file.id, auth_type)
        file_info = File.getOneById(file.parent)
        if not file_info:
            return []
        # 包装
        file_info = FileEntry(**file_info)
        return getAuth(file_info, auth_type)


# 获取父目录路径
def getParentPath(path: str):
    if not is_subdirectory(path, static_folder):
        return static_folder
    return os.path.dirname(path)


# 添加函数
def _baseAdd(parent: int, user: str, file: FileStorage | None, f_type: str, filename: str, cover: bool):
    p_info: dict
    # 去掉路径信息
    filename = os.path.basename(filename)
    if parent == 0:
        p_info = {
            'path': ""
        }
    else:
        p_info = File.getOneById(parent)
        if not p_info:
            return '所选目录不存在'
    # 父目录
    p_path = formatAbsPath(p_info['path'])
    # 存储绝对路径 相对于 static
    file_path = os.path.join(p_path, filename)
    # 系统相对路径
    rel_path = formatRelPath(file_path)
    # 检查父目录是否存在
    if not os.path.exists(p_path):
        os.makedirs(p_path)
    if not os.path.isdir(p_path):
        return "所选路径不是文件夹"
    database.begin()
    try:
        fileData = FileEntry()
        # 保存文件
        if f_type == 'file':
            # 检查文件是否存在
            if os.path.exists(file_path) and not cover:
                database.commit()
                return "文件已存在是否覆盖"
            file.save(file_path)
            fileData.size = os.path.getsize(file_path)
            fileData.suffix = os.path.splitext(file.filename)[1]
        elif f_type == 'dir':
            if os.path.exists(file_path):
                database.commit()
                return "文件夹已存在"
            os.mkdir(file_path)
            fileData.size = 0
            fileData.suffix = ""
        fileData.createTime = datetime.now(UTC)
        fileData.fType = f_type
        fileData.parent = parent
        fileData.name = filename or "no_name"
        fileData.createBy = user
        fileData.path = rel_path
        # 检查数据库是否有信息 决定到底是 添加还是修改
        old_info = File.getByPath(rel_path)
        if not old_info:
            File.add(fileData.__dict__)
        else:
            fileData.id = old_info.get('id') or 0
            File.updateFile(fileData.id, fileData.__dict__)
        database.commit()
        return True
    except Exception as e:
        logging.error(e.args[1], file_path)
        if os.path.exists(file_path):
            os.remove(file_path)
        database.rollback()
        return "上传文件出错"


# 给个系统添加文件 （不用鉴权）
def addFile(parent: int, user: str, file: FileStorage, cover: bool = False):
    if len(file.filename) == 0:
        return False
    return _baseAdd(parent, user, file, 'file', file.filename, cover)


# 添加文件夹
def addDir(parent: int, user: str, name: str, cover: bool = False):
    return _baseAdd(parent, user, None, 'dir', name, cover)


# 递归获取文件父目录文件信息集合
def _baseFileParentList(file: FileEntry) -> list[dict]:
    file_list = []
    if file.parent != 0:
        file_info = File.getOneById(file.parent)
        file_info = FileEntry(**file_info)
        file_list.append(file_info.__dict__)
        if file_info.parent == 0:
            return file_list
        file_list.extend(_baseFileParentList(file_info))
    return file_list


# 获取父文件集合
def getFileParentList(f_id: int):
    file_info = File.getOneById(f_id)
    if not file_info:
        return []
    file_list = _baseFileParentList(FileEntry(**file_info))
    # 翻转
    file_list.reverse()
    return file_list


# def updateFile
def _baseDelFile(f_ids: list[int]):
    if len(f_ids) == 0:
        return
    # 清除子文件信息
    for item in f_ids:
        # 获取 父文件是 f_ids 中一项的 所有子文件
        file_list = File.getListByParent(item)
        ids = [item.get('id') for item in file_list]
        # 先删除子文件
        _baseDelFile(ids)
    # 删除这些文件信息
    File.delList(f_ids)
    FileAuth.clearAuthInFId(f_ids)


# 删除文件
def delFile(f_ids: list[int]):
    database.begin()
    try:
        # 查询数据做保存
        file_list = File.getListInId(f_ids)
        # 先删除数据库数据
        _baseDelFile(f_ids)
        for item in file_list:
            file_path = formatAbsPath(item.get('path'))
            if not os.path.exists(file_path):
                continue
            if os.path.isdir(file_path):
                shutil.rmtree(file_path)
            else:
                os.remove(file_path)
        database.commit()
        return True
    except Exception as e:
        logging.error(e.args[1])
        database.rollback()
        return e.args[1]


# 获取路径信息
# def getPathInfo(path: str):
#     file = FileEntry()
#     path = formatAbsPath(path)
#     if not os.path.exists(path):
#         return None
#     file.size = os.path.getsize(path)
#     return file.__dict__


# 获取文件夹大小
def _baseGetFolderSize(f_path: str):
    total_size = 0
    total_file = 0
    for path, dirs, files in os.walk(f_path):
        for file in files:
            file_path = os.path.join(path, file)
            total_size += os.path.getsize(file_path)
            total_file = total_file + 1
    return {'file': total_file, 'size': total_size}


# 根据id获取文件夹大小
def getFolderSize(f_id: int):
    if f_id == 0:
        return _baseGetFolderSize(formatAbsPath(""))
    file_info = File.getOneById(f_id)
    if not file_info:
        return {'file': 0, 'size': 0}
    file_info = FileEntry(**file_info)
    if file_info.fType == 'file':
        return {'file': 1, 'size': file_info.size}
    return _baseGetFolderSize(formatAbsPath(file_info.path))


# 路径扫描函数 只能够扫描 文件夹
def _baseScan(parent_info: FileEntry, path: str, user: str = ""):
    # 获取 static 绝对路径
    static_path = formatAbsPath(os.path.join(parent_info.path, path))
    # 数据库路径
    file_path = formatRelPath(static_path)
    if not os.path.exists(static_path):
        return
    # 创建一个文件信息 将要添加到数据库的文件信息
    fileData = FileEntry()
    fileData.createTime = datetime.now(UTC)
    fileData.parent = parent_info.id
    fileData.name = os.path.split(file_path)[1] or "no_name"
    fileData.createBy = user
    fileData.path = file_path
    # 文件判断
    if os.path.isfile(static_path):
        fileData.fType = 'file'
        fileData.suffix = os.path.splitext(file_path)[1]
        fileData.size = os.path.getsize(static_path)
    # 文件夹判断
    elif os.path.isdir(static_path):
        fileData.fType = 'dir'
        fileData.suffix = ""
        fileData.size = 0
    # 获取文件信息
    file_info = File.getByPath(file_path)
    if file_info:
        fileData.id = file_info.get("id") or 0
        File.updateFile(fileData.id, fileData.__dict__)
    else:
        fileData.id = File.add(fileData.__dict__)
    if fileData.fType == 'dir':
        # print(f"文件夹{static_path}文件列表{os.listdir(static_path)}")
        for item in os.listdir(static_path):  # 遍历目录下的项
            # print(f"扫描文件夹{static_path} 下 {item}")
            _baseScan(fileData, item, user)


def scanDir(f_id: int, user: str):
    file_info: dict | FileEntry
    if f_id == 0:
        file_info = FileEntry()
        file_info.fType = 'dir'
    else:
        file_info = File.getOneById(f_id)
        if not file_info:
            return False
        file_info = FileEntry(**file_info)
    absPath = formatAbsPath(file_info.path)
    if not os.path.exists(absPath):
        return False
    if file_info.fType == 'file':
        return False
    file_list = os.listdir(absPath)
    for item in file_list:
        _baseScan(file_info, item, user)
    logging.info(f"文件夹{file_info.path}文件扫描结束")
    return True


# 获取文件列表
def getFileList(page: Page, cdn: dict):
    if cdn.get('parent') is None:
        cdn['parent'] = 0
    return File.getAll(page, cdn)


# 获取权限的包装
def getFileAuthList(f_id: int, auth_type: str = ""):
    file_info = File.getOneById(f_id)
    if not file_info:
        return []
    file_info = FileEntry(**file_info)
    return getAuth(file_info, auth_type)


# 修改文件权限
def updateFileAuth(f_id: int, data: dict, user: str = ""):
    if not authParam(data['authType'], ['token', 'group', 'user', 'role', 'public']):
        return False
    file_info = File.getOneById(f_id)
    if not file_info:
        return False
    try:
        database.begin()
        file_info = dict()
        file_info["updateBy"] = user
        file_info["updateTime"] = datetime.now(UTC)
        file_info["extend"] = 'N'
        File.updateFile(f_id, file_info)
        data['fid'] = f_id
        FileAuth.updateAuth(data)
        database.commit()
        return True
    except Exception as e:
        logging.error(e.args[1])
        database.rollback()
        return e.args[1]


# 给文件添加权限
def addFileAuth(f_id: int, data: dict, user: str = ""):
    data = formatParam(data, ['id'])
    if not authParam(data['authType'], ['token', 'group', 'user', 'role', 'public']):
        return False
    file_info = File.getOneById(f_id)
    if not file_info:
        return False
    try:
        database.begin()
        file_info["updateBy"] = user
        file_info["updateTime"] = datetime.now(UTC)
        file_info["extend"] = 'N'
        File.updateFile(f_id, file_info)
        data['fid'] = f_id
        data['createTime'] = datetime.now(UTC)
        FileAuth.addAuth(data)
        database.commit()
        return True
    except Exception as e:
        logging.error(e.args[1])
        database.rollback()
        return e.args[1]


# 删除文件权限
def delFileAuth(fid: int, a_ids: list[int], user):
    file_info = dict()
    try:
        database.begin()
        file_info["updateBy"] = user
        file_info["updateTime"] = datetime.now(UTC)
        file_info["extend"] = 'N'
        File.updateFile(fid, file_info)
        FileAuth.delAuthInAId(a_ids)
        database.commit()
        return True
    except Exception as e:
        logging.error(e.args[1])
        database.rollback()
        return e.args[1]


def changeFileExtend(f_id: int, extend: str, userName: str):
    if authParam(extend, ['Y', 'N']):
        return False
    if extend == 'Y':
        extend = 'N'
    else:
        extend = 'Y'
    try:
        File.updateFile(f_id, {
            "extend": extend,
            "updateTime": datetime.now(UTC),
            "updateBy": userName
        })
    except Exception as e:
        logging.error(e.args)
        return False
    return True

# print(addFileAuth(15, {"authType": "token", "canRead": "Y", "canWrite": "Y", 'token': 'asdfasdf'}))
# print(os.path.isdir("static/asdf\\asd"))
# directory = 'static/head'
# scanDir(1, 'superadmin')
# # 获取目录下的文件列表
# file_list = os.listdir(directory)
# print(os.path.relpath(os.path.abspath("static/head/1699236785.png"), 'static'))
